/** @file    Room.cpp
 *  @author  Alessandro Polo
 *  @version $Id: Room.cpp 2888 2010-08-11 08:31:27Z alex $
 *  @brief
 * File containing methods for the wosh::building::Room class.
 * The header for this class can be found in Room.h, check that file
 * for class description.
 ****************************************************************************/
/* Copyright (c) 2007-2010, WOSH - Wide Open Smart Home 
 * by Alessandro Polo - OpenSmartHome.com
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the OpenSmartHome.com WOSH nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY Alessandro Polo ''AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL Alessandro Polo BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ****************************************************************************/
 
 #include <framework/building/Room.h>
 
 #include <core/ObjectAllocator.h>
 #include <core/List.h>
 #include <core/MethodsCommon.h>
 
 #include <framework/building/Appliance.h>
 #include <framework/building/AudioBox.h>
 #include <framework/building/AppliancesCommon.h>

 #include <interfaces/Device.h>
 #include <interfaces/devices/SensorLight.h>
 #include <interfaces/devices/SensorMotion.h>


namespace wosh {
 namespace building {

 WOSH_REGISTER(wosh::building::Room, "wosh::building::BuildingObject", _Room_VERSION, _static_Room )

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS

Room::Room()
	: BuildingObject() {
	BuildingObject::setName( "wosh.building.Room", _GET_Abs(Utilities::randomLong()) );
 }

void Room::init() {
	Log(LOG_DEBUG, " Setting default properties and permissions.." );
	Properties.set( _Room_KEY_AccessTo, "", Permission(Permission::RW, Permission::RW, Permission::Read) );

	Properties.set( _Room_KEY_PresenceState, "", Permission(Permission::RW, Permission::Read, Permission::Read) );
	Properties.set( _Room_KEY_PresenceStateTs, Variant(0L, Variant::DATETIME), Permission(Permission::Read) );
	this->statePresence = Room::PRESENCE_UNKNOWN;
	setPresenceState(Room::PRESENCE_UNKNOWN, false);

	Properties.set( _Room_KEY_LightState, "", Permission(Permission::RW, Permission::Read, Permission::Read) );
	Properties.set( _Room_KEY_LightStateTs, Variant(0L, Variant::DATETIME), Permission(Permission::Read) );
	this->stateLight = Room::LIGHT_UNKNOWN;
	setLightState(Room::LIGHT_UNKNOWN, false);

	Properties.set( _Room_KEY_NoiseState, "", Permission(Permission::RW, Permission::Read, Permission::Read) );
	Properties.set( _Room_KEY_NoiseStateTs, Variant(0L, Variant::DATETIME), Permission(Permission::Read) );
	this->stateNoise = Room::NOISE_UNKNOWN;
	setNoiseState(Room::NOISE_UNKNOWN, false);

	Log(LOG_DEBUG, " Registering methods.." );
	MethodWRESULTwSecurityToken* mmLightsTurnOn = Methods.create<MethodWRESULTwSecurityToken>( _Room_METHOD_lights_on, "turn lights on", Permission::R_XR_XR__() );
	mmLightsTurnOn->setMethod( this, (MethodWRESULTwSecurityTokenPtr)&Room::mmDoLightsTurnOn );

	MethodWRESULTwSecurityToken* mmLightsTurnOff = Methods.create<MethodWRESULTwSecurityToken>( _Room_METHOD_lights_off, "turn lights off", Permission::R_XR_XR__() );
	mmLightsTurnOff->setMethod( this, (MethodWRESULTwSecurityTokenPtr)&Room::mmDoLightsTurnOff );

	BuildingObject::init();
 }

//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////// PROCESS CHILD MESSAGES

void Room::busMessageChild( const Message& childMessage ) {
	if ( childMessage.isEmpty() ) return;
	if ( !childMessage.getContent()->isFact() ) return;
	const Fact* fact = childMessage.getContent()->asFact();
	if ( fact == NULL ) return;

	if ( isChildA( childMessage.getSource(), Light::className() ) ) {
		Log(LOG_INFO, ":busMessageChild(%s) [Light]", childMessage.getSource().getName().c_str() );
		const Appliance_StateChanged* ast_event = fact->as<Appliance_StateChanged>();
		if ( ast_event != NULL && ast_event->isOn() )
			setLightState( Room::LIGHT_BRIGHT, true);
		// off state should be updated by light sensor
	 }
	else if ( isChildA( childMessage.getSource(), Appliance::className() ) ) {
		Log(LOG_INFO, ":busMessageChild(%s) [Appliance]", childMessage.getSource().getName().c_str() );
	 }

	else if ( isChildA( childMessage.getSource(), AudioBox::className() ) ) {
		Log(LOG_INFO, ":busMessageChild(%s) [AudioBox]", childMessage.getSource().getName().c_str() );
		const AudioBox_StateChanged* asc_event = fact->as<AudioBox_StateChanged>();
		if ( asc_event == NULL ) return;
		if ( asc_event->isOn() )
			setNoiseState( Room::NOISE_DETECTED, true, childMessage.getSecurityToken() );
		else if ( asc_event->isOff() )
			setNoiseState( Room::NOISE_SILENT, true, childMessage.getSecurityToken() );
		else
			setNoiseState( Room::NOISE_UNKNOWN, true, childMessage.getSecurityToken() );
	 }

	if ( fact->getEvent() == _Device_EVENT_StateChanged && fact->hasData() ) {
		if ( isChildA( childMessage.getSource(), wosh::interfaces::devices::SensorMotion::bundleType() ) ) {
			Log(LOG_INFO, ":busMessageChild(%s) [SensorMotion]", childMessage.getSource().getName().c_str() );
			if ( !fact->getData()->isKindOf<Variant>() )
				return;
			if ( fact->getData()->as<Variant>()->toInteger(-1) == 0 )
				setPresenceState( Room::PRESENCE_EMPTY, true, childMessage.getSecurityToken() );
			else
				setPresenceState( Room::PRESENCE_DETECTED, true, childMessage.getSecurityToken() );
		 }
		else if ( isChildA( childMessage.getSource(), wosh::interfaces::devices::SensorLight::bundleType() ) && fact->hasData() ) {
			Log(LOG_INFO, ":busMessageChild(%s) [SensorLight]", childMessage.getSource().getName().c_str() );
			if ( !fact->getData()->isKindOf<Variant>() )
				return;
			const Variant* data = fact->getData()->as<Variant>();
			if ( data->toInteger(-1) == 100 )
				setLightState( Room::LIGHT_DARK, true, childMessage.getSecurityToken() );
			else if ( data->toInteger(-1) == 0 )
				setLightState( Room::LIGHT_BRIGHT, true, childMessage.getSecurityToken() );
			else
				setLightState( Room::LIGHT_NATURAL, true, childMessage.getSecurityToken() );
		 }
	 }

 }

////////////////////////////////////////////////////////////////////////////////////// PROCESS CHILD MESSAGES
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////// GENERATED EVENTS

void Room::setPresenceState( Room::PRESENCE_STATE new_state, bool raise_event, const SecurityToken* source_token ) {
	Room::PRESENCE_STATE old_state = this->statePresence;
	Properties.transactionBeginWrite();
	if ( this->statePresence != new_state ) {
		this->statePresence = new_state;
		Properties.update( _Room_KEY_PresenceState, getPresenceStateAsString() );
	 }
	Properties.update( _Room_KEY_PresenceStateTs, Variant(Utilities::std_time(), Variant::DATETIME) );
	Properties.transactionEnd();
	if ( this->raiseAlwaysEvents || raise_event ) {
		Log(LOG_VERBOSE, ":setPresenceState(%s) Raising Event.. ", getPresenceStateAsString() );
		BuildingObject::raiseEvent( new Room_PresenceChanged(this->statePresence, old_state), source_token );
	 }
	else
		Log(LOG_DEBUG, ":setPresenceState(%s) Not Raising Event.", getPresenceStateAsString() );
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

void Room::setLightState( Room::LIGHT_STATE new_state, bool raise_event, const SecurityToken* source_token ) {
	Room::LIGHT_STATE old_state = this->stateLight;
	Properties.transactionBeginWrite();
	if ( this->stateLight != new_state ) {
		this->stateLight = new_state;
		Properties.update( _Room_KEY_LightState, getLightStateAsString() );
	 }
	Properties.update( _Room_KEY_LightStateTs, Variant(Utilities::std_time(), Variant::DATETIME) );
	Properties.transactionEnd();
	if ( this->raiseAlwaysEvents || raise_event ) {
		Log(LOG_VERBOSE, ":setLightState(%s) Raising Event.. ", getLightStateAsString() );
		BuildingObject::raiseEvent( new Room_LightChanged(this->stateLight, old_state), source_token );
	 }
	else
		Log(LOG_DEBUG, ":setLightState(%s) Not Raising Event.", getLightStateAsString() );
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

void Room::setNoiseState( Room::NOISE_STATE new_state, bool raise_event, const SecurityToken* source_token ) {
	Room::NOISE_STATE old_state = this->stateNoise;
	Properties.transactionBeginWrite();
	if ( this->stateNoise != new_state ) {
		this->stateNoise = new_state;
		Properties.update( _Room_KEY_NoiseState, getNoiseStateAsString() );
	 }
	Properties.update( _Room_KEY_NoiseStateTs, Variant(Utilities::std_time(), Variant::DATETIME) );
	Properties.transactionEnd();
	if ( this->raiseAlwaysEvents || raise_event ) {
		Log(LOG_VERBOSE, ":setNoiseState(%s) Raising Event.. ", getNoiseStateAsString() );
		BuildingObject::raiseEvent( new Room_NoiseChanged(this->stateNoise, old_state), source_token );
	 }
	else
		Log(LOG_DEBUG, ":setNoiseState(%s) Not Raising Event.", getNoiseStateAsString() );
 }

//////////////////////////////////////////////////////////////////////////////////////////// GENERATED EVENTS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////// DEVICE METHODS

WRESULT Room::mmDoLightsTurnOn( const SecurityToken* security ) {
	Log(LOG_INFO, ":mmDoLightsTurnOn()" );
	// state update is asynch. raised by Child-Device (as Fact)
	Request request( _Appliance_METHOD_on );
	request.setOmitResponse(true);
	BuildingObject::controlChildren( Light::className(), request, security );
	return WRET_OK;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT Room::mmDoLightsTurnOff( const SecurityToken* security ) {
	Log(LOG_INFO, ":mmDoLightsTurnOff()" );
	// state update is asynch. raised by Child-Device (as Fact)
	Request request( _Appliance_METHOD_off );
	request.setOmitResponse(true);
	BuildingObject::controlChildren( Light::className(), request, security );
	return WRET_OK;
 }

////////////////////////////////////////////////////////////////////////////////////////////// DEVICE METHODS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

bool Room::updateHash( DataHashContext* hash_context ) const {
	if ( hash_context == NULL ) return false;
	bool ret = BuildingObject::updateHash( hash_context );
	ret = hash_context->update( this->statePresence ) & ret;
	ret = hash_context->update( this->stateLight ) & ret;
	ret = hash_context->update( this->stateNoise ) & ret;
	return ret;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

const char* RoomPresenceStateStr[] = {
	"UNKNOWN",	// (PRESENCE_STATE)0
	"EMPTY",	// (PRESENCE_STATE)1
	"DETECTED"	// (PRESENCE_STATE)2
 };

Enum2StrFromArray(Room::getPresenceStateAsString, Room::PRESENCE_STATE, PRESENCE_EMPTY, PRESENCE_DETECTED, 0, RoomPresenceStateStr)

Str2EnumByArray(Room::getPresenceStateFromString, Room::PRESENCE_STATE, 0, PRESENCE_DETECTED, PRESENCE_UNKNOWN, RoomPresenceStateStr)

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

const char* RoomLightStateStr[] = {
	"UNKNOWN",	// (LIGHT_STATE)0
	"DARK",		// (LIGHT_STATE)1
	"BRIGHT",	// (LIGHT_STATE)2
	"NATURAL"	// (LIGHT_STATE)3
 };

Enum2StrFromArray(Room::getLightStateAsString, Room::LIGHT_STATE, LIGHT_DARK, LIGHT_NATURAL, 0, RoomLightStateStr)

Str2EnumByArray(Room::getLightStateFromString, Room::LIGHT_STATE, 0, LIGHT_NATURAL, LIGHT_UNKNOWN, RoomLightStateStr)

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

const char* RoomNoiseStateStr[] = {
	"UNKNOWN",	// (NOISE_STATE)0
	"SILENT",	// (NOISE_STATE)1
	"DETECTED"	// (NOISE_STATE)2
 };

Enum2StrFromArray(Room::getNoiseStateAsString, Room::NOISE_STATE, NOISE_SILENT, NOISE_DETECTED, 0, RoomNoiseStateStr)

Str2EnumByArray(Room::getNoiseStateFromString, Room::NOISE_STATE, 0, NOISE_DETECTED, NOISE_UNKNOWN, RoomNoiseStateStr)

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

Room::PRESENCE_STATE intToRoomPresenceStateEnum( int value ) {
	if ( value <= Room::PRESENCE_UNKNOWN ) return Room::PRESENCE_UNKNOWN;
	if ( value > Room::PRESENCE_DETECTED ) return Room::PRESENCE_UNKNOWN;
	return (Room::PRESENCE_STATE)value;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

 WOSH_REGISTER(wosh::building::Room_PresenceChanged, "wosh::Fact", 1.0, _static_Room_PresenceChanged )

Room_PresenceChanged::Room_PresenceChanged( Room::PRESENCE_STATE current_presence_state )
	: wosh::Fact( _Room_EVENT_PresenceChanged ) {
	setData( new Variant(current_presence_state) );
 }

Room_PresenceChanged::Room_PresenceChanged( Room::PRESENCE_STATE current_presence_state, Room::PRESENCE_STATE previous_presence_state )
	: wosh::Fact( _Room_EVENT_PresenceChanged ) {
	List* args = List::createFrom<Variant>( (int)current_presence_state, (int)previous_presence_state );
	setData(args);
 }

Room::PRESENCE_STATE Room_PresenceChanged::getPresenceState() const {
	const Variant* var = getDataItem<Variant>(0);
	if ( var == NULL ) return Room::PRESENCE_UNKNOWN;
	if ( var->isNumeric() ) return Room::PRESENCE_UNKNOWN;
	return intToRoomPresenceStateEnum(var->toInteger( (int)Room::PRESENCE_UNKNOWN ));
 }

Room::PRESENCE_STATE Room_PresenceChanged::getPreviousPresenceState() const {
	const Variant* var = getDataItem<Variant>(1);
	if ( var == NULL ) return Room::PRESENCE_UNKNOWN;
	if ( var->isNumeric() ) return Room::PRESENCE_UNKNOWN;
	return intToRoomPresenceStateEnum(var->toInteger( (int)Room::PRESENCE_UNKNOWN ));
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

Room::LIGHT_STATE intToRoomLightStateEnum( int value ) {
	if ( value <= Room::LIGHT_UNKNOWN ) return Room::LIGHT_UNKNOWN;
	if ( value > Room::LIGHT_NATURAL ) return Room::LIGHT_UNKNOWN;
	return (Room::LIGHT_STATE)value;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

 WOSH_REGISTER(wosh::building::Room_LightChanged, "wosh::Fact", 1.0, _static_Room_LightChanged )

Room_LightChanged::Room_LightChanged( Room::LIGHT_STATE current_presence_state )
	: wosh::Fact( _Room_EVENT_LightChanged ) {
	setData( new Variant(current_presence_state) );
 }

Room_LightChanged::Room_LightChanged( Room::LIGHT_STATE current_presence_state, Room::LIGHT_STATE previous_presence_state )
	: wosh::Fact( _Room_EVENT_LightChanged ) {
	List* args = List::createFrom<Variant>( (int)current_presence_state, (int)previous_presence_state );
	setData(args);
 }

Room::LIGHT_STATE Room_LightChanged::getLightState() const {
	const Variant* var = getDataItem<Variant>(0);
	if ( var == NULL ) return Room::LIGHT_UNKNOWN;
	if ( var->isNumeric() ) return Room::LIGHT_UNKNOWN;
	return intToRoomLightStateEnum(var->toInteger( (int)Room::LIGHT_UNKNOWN ));
 }

Room::LIGHT_STATE Room_LightChanged::getPreviousLightState() const {
	const Variant* var = getDataItem<Variant>(1);
	if ( var == NULL ) return Room::LIGHT_UNKNOWN;
	if ( var->isNumeric() ) return Room::LIGHT_UNKNOWN;
	return intToRoomLightStateEnum(var->toInteger( (int)Room::LIGHT_UNKNOWN ));
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

Room::NOISE_STATE intToRoomNoiseStateEnum( int value ) {
	if ( value <= Room::NOISE_UNKNOWN ) return Room::NOISE_UNKNOWN;
	if ( value > Room::NOISE_DETECTED ) return Room::NOISE_UNKNOWN;
	return (Room::NOISE_STATE)value;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

 WOSH_REGISTER(wosh::building::Room_NoiseChanged, "wosh::Fact", 1.0, _static_Room_NoiseChanged )

Room_NoiseChanged::Room_NoiseChanged( Room::NOISE_STATE current_noise_state )
	: wosh::Fact( _Room_EVENT_PresenceChanged ) {
	setData( new Variant(current_noise_state) );
 }

Room_NoiseChanged::Room_NoiseChanged( Room::NOISE_STATE current_noise_state, Room::NOISE_STATE previous_noise_state )
	: wosh::Fact( _Room_EVENT_PresenceChanged ) {
	List* args = List::createFrom<Variant>( (int)current_noise_state, (int)previous_noise_state );
	setData(args);
 }

Room::NOISE_STATE Room_NoiseChanged::getNoiseState() const {
	const Variant* var = getDataItem<Variant>(0);
	if ( var == NULL ) return Room::NOISE_UNKNOWN;
	if ( var->isNumeric() ) return Room::NOISE_UNKNOWN;
	return intToRoomNoiseStateEnum(var->toInteger( (int)Room::NOISE_UNKNOWN ));
 }

Room::NOISE_STATE Room_NoiseChanged::getPreviousNoiseState() const {
	const Variant* var = getDataItem<Variant>(1);
	if ( var == NULL ) return Room::NOISE_UNKNOWN;
	if ( var->isNumeric() ) return Room::NOISE_UNKNOWN;
	return intToRoomNoiseStateEnum(var->toInteger( (int)Room::NOISE_UNKNOWN ));
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////



 }; // namespace building
}; // namespace wosh
